home *** CD-ROM | disk | FTP | other *** search
/ CD/PC Actual 31 / PC Actual CD 31.iso / dists / SRC / SLIBEXEC.AA / SLIBEXEC / libexec / atrun / atrun.c next >
Encoding:
C/C++ Source or Header  |  1998-08-02  |  12.1 KB  |  502 lines

  1. /* 
  2.  *  atrun.c - run jobs queued by at; run with root privileges.
  3.  *  Copyright (C) 1993, 1994 Thomas Koenig
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer.
  10.  * 2. The name of the author(s) may not be used to endorse or promote
  11.  *    products derived from this software without specific prior written
  12.  *    permission.
  13.  *
  14.  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
  15.  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  16.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  17.  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
  18.  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  19.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  20.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  21.  * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  23.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24.  */
  25.  
  26. #ifndef lint
  27. static const char rcsid[] =
  28.     "$Id: atrun.c,v 1.13 1998/08/02 16:44:17 bde Exp $";
  29. #endif /* not lint */
  30.  
  31. /* System Headers */
  32.  
  33. #include <sys/fcntl.h>
  34. #include <sys/types.h>
  35. #include <sys/stat.h>
  36. #include <sys/wait.h>
  37. #include <sys/param.h>
  38. #include <ctype.h>
  39. #include <dirent.h>
  40. #include <err.h>
  41. #include <grp.h>
  42. #include <pwd.h>
  43. #include <signal.h>
  44. #include <stddef.h>
  45. #include <stdio.h>
  46. #include <stdlib.h>
  47. #include <string.h>
  48. #include <syslog.h>
  49. #include <time.h>
  50. #include <unistd.h>
  51. #include <utmp.h>
  52. #ifdef __FreeBSD__
  53. #include <paths.h>
  54. #else
  55. #include <getopt.h>
  56. #endif
  57.  
  58. #if (MAXLOGNAME-1) > UT_NAMESIZE
  59. #define LOGNAMESIZE UT_NAMESIZE
  60. #else
  61. #define LOGNAMESIZE (MAXLOGNAME-1)
  62. #endif
  63.  
  64. /* Local headers */
  65.  
  66. #include "gloadavg.h"
  67. #define MAIN
  68. #include "privs.h"
  69.  
  70. /* Macros */
  71.  
  72. #ifndef ATJOB_DIR
  73. #define ATJOB_DIR "/usr/spool/atjobs/"
  74. #endif
  75.  
  76. #ifndef ATSPOOL_DIR
  77. #define ATSPOOL_DIR "/usr/spool/atspool/"
  78. #endif
  79.  
  80. #ifndef LOADAVG_MX
  81. #define LOADAVG_MX 1.5
  82. #endif
  83.  
  84. /* File scope variables */
  85.  
  86. static debug = 0;
  87.  
  88. void perr(const char *a);
  89. static void usage __P((void));
  90.  
  91. /* Local functions */
  92. static int
  93. write_string(int fd, const char* a)
  94. {
  95.     return write(fd, a, strlen(a));
  96. }
  97.  
  98. #undef DEBUG_FORK
  99. #ifdef DEBUG_FORK
  100. static pid_t
  101. myfork()
  102. {
  103.     pid_t res;
  104.     res = fork();
  105.     if (res == 0)
  106.         kill(getpid(),SIGSTOP);
  107.     return res;
  108. }
  109.  
  110. #define fork myfork
  111. #endif
  112.  
  113. static void
  114. run_file(const char *filename, uid_t uid, gid_t gid)
  115. {
  116. /* Run a file by by spawning off a process which redirects I/O,
  117.  * spawns a subshell, then waits for it to complete and sends
  118.  * mail to the user.
  119.  */
  120.     pid_t pid;
  121.     int fd_out, fd_in;
  122.     int queue;
  123.     char mailbuf[LOGNAMESIZE + 1], fmt[49];
  124.     char *mailname = NULL;
  125.     FILE *stream;
  126.     int send_mail = 0;
  127.     struct stat buf, lbuf;
  128.     off_t size;
  129.     struct passwd *pentry;
  130.     int fflags;
  131.     long nuid;
  132.     long ngid;
  133.  
  134.  
  135.     PRIV_START
  136.  
  137.     if (chmod(filename, S_IRUSR) != 0)
  138.     {
  139.     perr("cannot change file permissions");
  140.     }
  141.  
  142.     PRIV_END
  143.  
  144.     pid = fork();
  145.     if (pid == -1)
  146.     perr("cannot fork");
  147.     
  148.     else if (pid != 0)
  149.     return;
  150.  
  151.     /* Let's see who we mail to.  Hopefully, we can read it from
  152.      * the command file; if not, send it to the owner, or, failing that,
  153.      * to root.
  154.      */
  155.  
  156.     pentry = getpwuid(uid);
  157.     if (pentry == NULL)
  158.     {
  159.     syslog(LOG_ERR,"Userid %lu not found - aborting job %s",
  160.            (unsigned long) uid, filename);
  161.         exit(EXIT_FAILURE);
  162.     }
  163.     PRIV_START
  164.  
  165.     stream=fopen(filename, "r");
  166.  
  167.     PRIV_END
  168.  
  169. #ifdef __FreeBSD__
  170.     if (pentry->pw_expire && time(NULL) >= pentry->pw_expire)
  171.     {
  172.     syslog(LOG_ERR, "Userid %lu is expired - aborting job %s",
  173.         (unsigned long) uid, filename);
  174.     exit(EXIT_FAILURE);
  175.     }
  176. #endif
  177.  
  178.     if (stream == NULL)
  179.     perr("cannot open input file");
  180.  
  181.     if ((fd_in = dup(fileno(stream))) <0)
  182.     perr("error duplicating input file descriptor");
  183.  
  184.     if (fstat(fd_in, &buf) == -1)
  185.     perr("error in fstat of input file descriptor");
  186.  
  187.     if (lstat(filename, &lbuf) == -1)
  188.     perr("error in fstat of input file");
  189.  
  190.     if (S_ISLNK(lbuf.st_mode)) {
  191.     syslog(LOG_ERR,"Symbolic link encountered in job %s - aborting",
  192.         filename);
  193.     exit(EXIT_FAILURE);
  194.     }
  195.     if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) ||
  196.         (lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) ||
  197.         (lbuf.st_size!=buf.st_size)) {
  198.     syslog(LOG_ERR,"Somebody changed files from under us for job %s - "
  199.     "aborting",filename);
  200.     exit(EXIT_FAILURE);
  201.     }
  202.     if (buf.st_nlink > 1) {
  203.     syslog(LOG_ERR,"Someboy is trying to run a linked script for job %s",
  204.         filename);
  205.     exit(EXIT_FAILURE);
  206.     }
  207.     if ((fflags = fcntl(fd_in, F_GETFD)) <0)
  208.     perr("error in fcntl");
  209.  
  210.     fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC);
  211.  
  212.     snprintf(fmt, 49, "#!/bin/sh\n# atrun uid=%%ld gid=%%ld\n# mail %%%ds %%d",
  213.                           LOGNAMESIZE);
  214.     if (fscanf(stream, fmt, &nuid, &ngid, mailbuf, &send_mail) != 4) {
  215.     syslog(LOG_ERR,"File %s is in wrong format - aborting", filename);
  216.     exit(EXIT_FAILURE);
  217.     }
  218.     if (mailbuf[0] == '-') {
  219.     syslog(LOG_ERR,"illegal mail name %s in %s",mailbuf,filename);
  220.     exit(EXIT_FAILURE);
  221.     }
  222.     mailname = mailbuf;
  223.     if (nuid != uid) {
  224.     syslog(LOG_ERR,"Job %s - userid %ld does not match file uid %lu",
  225.         filename, nuid, (unsigned long)uid);
  226.     exit(EXIT_FAILURE);
  227.     }
  228.     if (ngid != gid) {
  229.     syslog(LOG_ERR,"Job %s - groupid %ld does not match file gid %lu",
  230.         filename, ngid, (unsigned long)gid);
  231.     exit(EXIT_FAILURE);
  232.     }
  233.     fclose(stream);
  234.     if (chdir(ATSPOOL_DIR) < 0)
  235.     perr("cannot chdir to " ATSPOOL_DIR);
  236.     
  237.     /* Create a file to hold the output of the job we are about to run.
  238.      * Write the mail header.
  239.      */    
  240.     if((fd_out=open(filename,
  241.         O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0)
  242.     perr("cannot create output file");
  243.  
  244.     write_string(fd_out, "Subject: Output from your job ");
  245.     write_string(fd_out, filename);
  246.     write_string(fd_out, "\n\n");
  247.     fstat(fd_out, &buf);
  248.     size = buf.st_size;
  249.  
  250.     close(STDIN_FILENO);
  251.     close(STDOUT_FILENO);
  252.     close(STDERR_FILENO);
  253.  
  254.     pid = fork();
  255.     if (pid < 0)
  256.     perr("error in fork");
  257.  
  258.     else if (pid == 0)
  259.     {
  260.     char *nul = NULL;
  261.     char **nenvp = &nul;
  262.  
  263.     /* Set up things for the child; we want standard input from the input file,
  264.      * and standard output and error sent to our output file.
  265.      */
  266.  
  267.     if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0)
  268.         perr("error in lseek");
  269.  
  270.     if (dup(fd_in) != STDIN_FILENO)
  271.         perr("error in I/O redirection");
  272.  
  273.     if (dup(fd_out) != STDOUT_FILENO)
  274.         perr("error in I/O redirection");
  275.  
  276.     if (dup(fd_out) != STDERR_FILENO)
  277.         perr("error in I/O redirection");
  278.  
  279.     close(fd_in);
  280.     close(fd_out);
  281.     if (chdir(ATJOB_DIR) < 0)
  282.         perr("cannot chdir to " ATJOB_DIR);
  283.  
  284.     queue = *filename;
  285.  
  286.     PRIV_START
  287.  
  288.         nice(tolower(queue) - 'a');
  289.     
  290.     if (initgroups(pentry->pw_name,pentry->pw_gid))
  291.         perr("cannot delete saved userids");
  292.  
  293.     if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0)
  294.         perr("cannot change group");
  295.  
  296.     if (setlogin(pentry->pw_name))
  297.         perr("cannot set login name");
  298.  
  299.     if (setuid(uid) < 0 || seteuid(uid) < 0)
  300.         perr("cannot set user id");
  301.  
  302.     if (chdir(pentry->pw_dir))
  303.         chdir("/");
  304.  
  305.     if(execle("/bin/sh","sh",(char *) NULL, nenvp) != 0)
  306.         perr("exec failed for /bin/sh");
  307.  
  308.     PRIV_END
  309.     }
  310.     /* We're the parent.  Let's wait.
  311.      */
  312.     close(fd_in);
  313.     close(fd_out);
  314.     waitpid(pid, (int *) NULL, 0);
  315.  
  316.     /* Send mail.  Unlink the output file first, so it is deleted after
  317.      * the run.
  318.      */
  319.     stat(filename, &buf);
  320.     if (open(filename, O_RDONLY) != STDIN_FILENO)
  321.         perr("open of jobfile failed");
  322.  
  323.     unlink(filename);
  324.     if ((buf.st_size != size) || send_mail)
  325.     {    
  326.     PRIV_START
  327.  
  328.     if (initgroups(pentry->pw_name,pentry->pw_gid))
  329.         perr("cannot delete saved userids");
  330.  
  331.     if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0)
  332.         perr("cannot change group");
  333.  
  334.     if (setlogin(pentry->pw_name))
  335.         perr("cannot set login name");
  336.  
  337.     if (setuid(uid) < 0 || seteuid(uid) < 0)
  338.         perr("cannot set user id");
  339.  
  340.     if (chdir(pentry->pw_dir))
  341.         chdir("/");
  342.  
  343. #ifdef __FreeBSD__
  344.     execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service",
  345.             "-odi", "-oem",
  346.             mailname, (char *) NULL);
  347. #else
  348.         execl(MAIL_CMD, MAIL_CMD, mailname, (char *) NULL);
  349. #endif
  350.         perr("exec failed for mail command");
  351.  
  352.     PRIV_END
  353.     }
  354.     exit(EXIT_SUCCESS);
  355. }
  356.  
  357. /* Global functions */
  358.  
  359. /* Needed in gloadavg.c */
  360. void
  361. perr(const char *a)
  362. {
  363.     if (debug)
  364.     {
  365.     warn("%s", a);
  366.     }
  367.     else
  368.     syslog(LOG_ERR, "%s: %m", a);
  369.  
  370.     exit(EXIT_FAILURE);
  371. }
  372.  
  373. int
  374. main(int argc, char *argv[])
  375. {
  376. /* Browse through  ATJOB_DIR, checking all the jobfiles wether they should
  377.  * be executed and or deleted. The queue is coded into the first byte of
  378.  * the job filename, the date (in minutes since Eon) as a hex number in the
  379.  * following eight bytes, followed by a dot and a serial number.  A file
  380.  * which has not been executed yet is denoted by its execute - bit set.
  381.  * For those files which are to be executed, run_file() is called, which forks
  382.  * off a child which takes care of I/O redirection, forks off another child
  383.  * for execution and yet another one, optionally, for sending mail.
  384.  * Files which already have run are removed during the next invocation.
  385.  */
  386.     DIR *spool;
  387.     struct dirent *dirent;
  388.     struct stat buf;
  389.     unsigned long ctm;
  390.     unsigned long jobno;
  391.     char queue;
  392.     time_t now, run_time;
  393.     char batch_name[] = "Z2345678901234";
  394.     uid_t batch_uid;
  395.     gid_t batch_gid;
  396.     int c;
  397.     int run_batch;
  398.     double load_avg = LOADAVG_MX;
  399.  
  400. /* We don't need root privileges all the time; running under uid and gid daemon
  401.  * is fine.
  402.  */
  403.  
  404.     RELINQUISH_PRIVS_ROOT(DAEMON_UID, DAEMON_GID)
  405.  
  406.     openlog("atrun", LOG_PID, LOG_CRON);
  407.  
  408.     opterr = 0;
  409.     while((c=getopt(argc, argv, "dl:"))!= -1)
  410.     {
  411.     switch (c)
  412.     {
  413.     case 'l': 
  414.         if (sscanf(optarg, "%lf", &load_avg) != 1)
  415.         perr("garbled option -l");
  416.         if (load_avg <= 0.)
  417.         load_avg = LOADAVG_MX;
  418.         break;
  419.  
  420.     case 'd':
  421.         debug ++;
  422.         break;
  423.  
  424.     case '?':
  425.     default:
  426.         usage();
  427.     }
  428.     }
  429.  
  430.     if (chdir(ATJOB_DIR) != 0)
  431.     perr("cannot change to " ATJOB_DIR);
  432.  
  433.     /* Main loop. Open spool directory for reading and look over all the
  434.      * files in there. If the filename indicates that the job should be run
  435.      * and the x bit is set, fork off a child which sets its user and group
  436.      * id to that of the files and exec a /bin/sh which executes the shell
  437.      * script. Unlink older files if they should no longer be run.  For
  438.      * deletion, their r bit has to be turned on.
  439.      *
  440.      * Also, pick the oldest batch job to run, at most one per invocation of
  441.      * atrun.
  442.      */
  443.     if ((spool = opendir(".")) == NULL)
  444.     perr("cannot read " ATJOB_DIR);
  445.  
  446.     now = time(NULL);
  447.     run_batch = 0;
  448.     batch_uid = (uid_t) -1;
  449.     batch_gid = (gid_t) -1;
  450.  
  451.     while ((dirent = readdir(spool)) != NULL) {
  452.     if (stat(dirent->d_name,&buf) != 0)
  453.         perr("cannot stat in " ATJOB_DIR);
  454.  
  455.     /* We don't want directories
  456.      */
  457.     if (!S_ISREG(buf.st_mode)) 
  458.         continue;
  459.  
  460.     if (sscanf(dirent->d_name,"%c%5lx%8lx",&queue,&jobno,&ctm) != 3)
  461.         continue;
  462.  
  463.     run_time = (time_t) ctm*60;
  464.  
  465.     if ((S_IXUSR & buf.st_mode) && (run_time <=now)) {
  466.         if (isupper(queue) && (strcmp(batch_name,dirent->d_name) > 0)) {
  467.         run_batch = 1;
  468.         strncpy(batch_name, dirent->d_name, sizeof(batch_name));
  469.         batch_uid = buf.st_uid;
  470.         batch_gid = buf.st_gid;
  471.         }
  472.     
  473.     /* The file is executable and old enough
  474.      */
  475.         if (islower(queue))
  476.         run_file(dirent->d_name, buf.st_uid, buf.st_gid);
  477.     }
  478.     /*  Delete older files
  479.      */
  480.     if ((run_time < now) && !(S_IXUSR & buf.st_mode) && (S_IRUSR & buf.st_mode))
  481.         unlink(dirent->d_name);
  482.     }
  483.     /* run the single batch file, if any
  484.     */
  485.     if (run_batch && (gloadavg() < load_avg))
  486.     run_file(batch_name, batch_uid, batch_gid);
  487.  
  488.     closelog();
  489.     exit(EXIT_SUCCESS);
  490. }
  491.  
  492. static void
  493. usage()
  494. {
  495.     if (debug)
  496.     fprintf(stderr, "usage: atrun [-l load_avg] [-d]\n");
  497.     else
  498.     syslog(LOG_ERR, "usage: atrun [-l load_avg] [-d]"); 
  499.  
  500.     exit(EXIT_FAILURE);
  501. }
  502.